package drds.data_propagate.parse.ddl;

import drds.data_propagate.binlog_event.secondary.EventType;
import drds.data_propagate.binlog_event_filter.PatternUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.oro.text.regex.Perl5Matcher;

/**
 * 简单的ddl解析工具类，后续可使用cobar/druid的SqlParser进行语法树解析
 *
 * <pre>
 * 解析支持：
 * a. 带schema: retl.retl_mark
 * b. 带引号` :  `retl.retl_mark`
 * c. 存在换行符： create_table tableName \n `retl.retl_mark`
 * </pre>
 * <p>
 * http://dev.mysql.com/doc/refman/5.6/en/sql-syntax-data-definition.html
 */
public class SimpleDdlParser {

    public static final String create_table_pattern = "^\\s*create_table\\s*(temporary)?\\s*tableName\\s*(.*)$";
    public static final String drop_table_pattern = "^\\s*drop\\s*(temporary)?\\s*tableName\\s*(.*)$";
    public static final String alert_table_pattern = "^\\s*alter_table\\s*(ignore)?\\s*tableName\\s*(.*)$";
    public static final String truncate_table_pattern = "^\\s*truncate\\s*(tableName)?\\s*(.*)$";
    //
    public static final String table_pattern = "^(if\\s*not\\s*exists\\s*)?(if\\s*exists\\s*)?(`?.+?`?[;\\(\\s]+?)?.*$"; // 采用非贪婪模式
    //
    public static final String insert_merge_replace_pattern = "^\\s*(insert|merge|replace)(.*)$";
    public static final String update_pattern = "^\\s*update(.*)$";
    public static final String delete_pattern = "^\\s*delete(.*)$";
    //
    public static final String rename_table_pattern = "^\\s*rename_table\\s+tableName\\s+(.+?)\\s+decode\\s+(.+?)$";
    public static final String rename_remnant_pattern = "^\\s*(.+?)\\s+decode\\s+(.+?)$";

    /**
     * <pre>
     * CREATE [UNIQUE|FULLTEXT|SPATIAL] INDEX index_name
     *         [index_type]
     *         ON tbl_name (index_col_name,...)
     *         [algorithm_option | lock_option] ...
     *
     * http://dev.mysql.com/doc/refman/5.6/en/create-index.html
     * </pre>
     */
    public static final String create_index_pattern = "^\\s*create_table\\s*(uniqueIndex)?(fulltext)?(spatial)?\\s*index\\s*(.*?)\\s+on\\s+(.*?)$";
    public static final String drop_index_pattern = "^\\s*drop\\s*index\\s*(.*?)\\s+on\\s+(.*?)$";

    public static DdlResult parse(String queryString, String schmeaName) {
        queryString = removeComment(queryString); // 去除/* */的sql注释内容
        DdlResult parseDdl = parseDdl(schmeaName, alert_table_pattern, 2, queryString);
        if (parseDdl != null) {
            parseDdl.setEventType(EventType.alter_table);
            return parseDdl;
        }

        parseDdl = parseDdl(schmeaName, create_table_pattern, 2, queryString);
        if (parseDdl != null) {
            parseDdl.setEventType(EventType.create_table);
            return parseDdl;
        }

        parseDdl = parseDdl(schmeaName, drop_table_pattern, 2, queryString);
        if (parseDdl != null) {
            parseDdl.setEventType(EventType.drop_table);
            return parseDdl;
        }

        parseDdl = parseDdl(schmeaName, truncate_table_pattern, 2, queryString);
        if (parseDdl != null) {
            parseDdl.setEventType(EventType.truncate);
            return parseDdl;
        }

        parseDdl = parseRename(queryString, schmeaName, rename_table_pattern);
        if (parseDdl != null) {
            parseDdl.setEventType(EventType.rename_table);

            String[] renameStrings = queryString.split(",");
            if (renameStrings.length > 1) {
                DdlResult lastResult = parseDdl;
                for (int i = 1; i < renameStrings.length; i++) {
                    DdlResult ddlResult = parseRename(renameStrings[i], schmeaName, rename_remnant_pattern);
                    ddlResult.setEventType(EventType.rename_table);
                    lastResult.setRenameTableResult(ddlResult);
                    lastResult = ddlResult;
                }
            }

            return parseDdl;
        }

        parseDdl = parseDdl(schmeaName, create_index_pattern, 5, queryString);
        if (parseDdl != null) {
            parseDdl.setEventType(EventType.create_index);
            return parseDdl;
        }

        parseDdl = parseDdl(schmeaName, drop_index_pattern, 2, queryString);
        if (parseDdl != null) {
            parseDdl.setEventType(EventType.delete_index);
            return parseDdl;
        }

        parseDdl = new DdlResult(schmeaName);
        if (isDml(queryString, insert_merge_replace_pattern)) {
            parseDdl.setEventType(EventType.insert);
            return parseDdl;
        }

        if (isDml(queryString, update_pattern)) {
            parseDdl.setEventType(EventType.update);
            return parseDdl;
        }

        if (isDml(queryString, delete_pattern)) {
            parseDdl.setEventType(EventType.delete);
            return parseDdl;
        }

        parseDdl.setEventType(EventType.query);
        return parseDdl;
    }

    private static DdlResult parseDdl(String schmeaName, String pattern, int index, String queryString) {
        Perl5Matcher perl5Matcher = new Perl5Matcher();
        if (perl5Matcher.matches(queryString, PatternUtils.getPattern(pattern))) {
            DdlResult ddlResult = parseTableName(perl5Matcher.getMatch().group(index), schmeaName);
            return ddlResult != null ? ddlResult : new DdlResult(schmeaName); // 无法解析时，直接返回schmea，进行兼容处理
        }

        return null;
    }

    private static boolean isDml(String queryString, String pattern) {
        Perl5Matcher matcher = new Perl5Matcher();
        if (matcher.matches(queryString, PatternUtils.getPattern(pattern))) {
            return true;
        } else {
            return false;
        }
    }

    private static DdlResult parseRename(String queryString, String schmeaName, String pattern) {
        Perl5Matcher matcher = new Perl5Matcher();
        if (matcher.matches(queryString, PatternUtils.getPattern(pattern))) {
            DdlResult orign = parseTableName(matcher.getMatch().group(1), schmeaName);
            DdlResult target = parseTableName(matcher.getMatch().group(2), schmeaName);
            if (orign != null && target != null) {
                return new DdlResult(target.getSchemaName(), target.getTableName(), orign.getSchemaName(),
                        orign.getTableName());
            }
        }

        return null;
    }

    private static DdlResult parseTableName(String matchString, String schmeaName) {
        Perl5Matcher tableMatcher = new Perl5Matcher();
        matchString = matchString + " ";
        if (tableMatcher.matches(matchString, PatternUtils.getPattern(table_pattern))) {
            String tableString = tableMatcher.getMatch().group(3);
            if (StringUtils.isEmpty(tableString)) {
                return null;
            }

            tableString = StringUtils.removeEnd(tableString, ";");
            tableString = StringUtils.removeEnd(tableString, "(");
            tableString = StringUtils.trim(tableString);
            // 特殊处理引号`
            tableString = removeEscape(tableString);
            // 处理schema.table的写法
            String names[] = StringUtils.split(tableString, ".");
            if (names.length == 0) {
                return null;
            }

            if (names != null && names.length > 1) {
                return new DdlResult(removeEscape(names[0]), removeEscape(names[1]));
            } else {
                return new DdlResult(schmeaName, removeEscape(names[0]));
            }
        }

        return null;
    }

    private static String removeEscape(String str) {
        String result = StringUtils.removeEnd(str, "`");
        result = StringUtils.removeStart(result, "`");
        return result;
    }

    private static String removeComment(String sql) {
        if (sql == null) {
            return null;
        }

        String start = "/*";
        String end = "*/";
        while (true) {
            // 循环找到所有的注释
            int index0 = sql.indexOf(start);
            if (index0 == -1) {
                return sql;
            }
            int index1 = sql.indexOf(end, index0);
            if (index1 == -1) {
                return sql;
            }
            StringBuilder sb = new StringBuilder();
            sb.append(sql.substring(0, index0));
            sb.append(" ");
            sb.append(sql.substring(index1 + end.length()));
            sql = sb.toString();
        }
    }
}
